/*
 	PureMVC UnifiedLoader Utility
 	Copyright (c) 2008 neil manuell
  	Your reuse is governed by the Creative Commons Attribution 3.0 License
 */
package org.puremvc.as3.multicore.utilities.unifiedloader.loader
{

	import flash.events.*;
	import flash.utils.Dictionary;
	
	import org.puremvc.as3.multicore.utilities.unifiedloader.clients.LoaderClientBase;
	import org.puremvc.as3.multicore.utilities.unifiedloader.errors.LoadQueueError;
	import org.puremvc.as3.multicore.utilities.unifiedloader.errors.RegistrationError;
	import org.puremvc.as3.multicore.utilities.unifiedloader.events.*;
	import org.puremvc.as3.multicore.utilities.unifiedloader.interfaces.*;
	
	public class UnifiedLoader extends EventDispatcher
	{
		private var mDataMap:Dictionary;
		private var mClientMap:Object;
		private var mLoadQueue:Array;
		private var mIsLoading:Boolean;
		private var mIsAbortLoading:Boolean;
		private var mIsAutoLoad:Boolean;
		private var mIsProrityChanged:Boolean;
		private var mCount:int;
		private var mNumInQueue:int;
		private var mCurrentItemBytesLoaded:Number;
        private var mCurrentItemBytesTotal:Number;
        private var mQueueLoadProgress:Number;
		private var mQueueSort:Function;
		
		
		/**
		 * the constructor
		 */
		public function UnifiedLoader() 
		{
			init();
		}
		
		/**
		 * indicates whether the load queue is active
		 */
		public function get isActive():Boolean{
			return mIsLoading;
		}
		
		/**
		 * the number of items left in the load queue
		 */
		public function get queueLength():int{
			return mLoadQueue.length;
		}
		
		/**
		 * the function that defines the load queue order
		 */
		public function set queueSortFunction(param:Function):void
		{
			mQueueSort = param;
			mIsProrityChanged = true;
		}
		
		/**
		 * defines whether the load queue should automatically start when an item has been added
		 */
		public function get autoLoad():Boolean
		{
			return mIsAutoLoad;
		}
		
		/**
		 * @private
		 */
		public function set autoLoad(param:Boolean):void
		{
			mIsAutoLoad = param;
		}
		/**
		 * returns the IAssetInfo instance associated with the url.  
		 * If one doesn't exist, it will create one.  You have to add this to the
		 * loadQueue with the addToQueue() method.
		 * @param url the url of the asset to be loaded
		 * @return the IAssetInfo instance
		 * 
		 */
		public function getAssetInfo(url:String):IAssetInfo
		{
			var request:LoadRequest;
			
			request = mDataMap[url];
			
			if (request == null)
			{
				mDataMap[url] = new LoadRequest(url)
				return mDataMap[url];
			}else
			{
				return request;
			}
		
		}
		/**
		 * checks if an IAssetInfo instance is stored in the loader
		 * @param url the url of the asset to checks
		 * @return whether the specified url has a IAssetInfo instance stored in the Loader
		 * 
		 */
		public function hasAssetInfo(url:String):Boolean
		{
			return Boolean(mDataMap[url]);
		}
		
		/**
		 * indicates the load state of the IAssetInfo indicated by the url. 
		 * @param url the url associated with the IAssetInfo whose status is to be retrieved
		 * @return the load status of the IAssetInfo associated with the url
		 * @see org.puremvc.as3.multicore.utilities.unifiedloader.etypes.AssetInfoStatus
		 * 
		 */
		public function getStatus(url:String):String
		{
			var request:LoadRequest;
			
			request = mDataMap[url];
			
			if (request == null)
			{
				return AssetInfoStatus.NON_EXISTENT;
			}else
			{
				return request.status;
			}
		}
		
		/**
		 * deletes the IAssetInfo associated with the url from the internal registery.
		 * @param url the url associated with the IAssetInfo to be deleted
		 * 
		 */
		public function deleteAssetInfo(url:String):void
		{
			var request:LoadRequest;
			
			request = mDataMap[url];
			
			if (request == null)
			{
				throw new RegistrationError (url);
			}else
			{
				request.deconstruct();
				delete mDataMap[url];
			}

		}
		
		/**
		 * This adds an IAssetInfo to the load queue. The IAssetInfo must first be
		 * aquired from the getAsset() method.
		 * @param param the IAssetInfo instance to add to the load queue
		 * @see #getAsset
		 * 
		 */
		public function addToQueue(param:IAssetInfo):void{
			
			var request:LoadRequest;
			
			request = LoadRequest(param)
			errorCheck(request)
			request.status = AssetInfoStatus.QUEUED;
			loadQueue.push(request);
			mNumInQueue++;
			mIsProrityChanged = true;
			
			if(!mIsLoading && mIsAutoLoad)
			{
				startQueue();		
			}
		}
		
		/**
		 * Stops the load queue
		 * 
		 */
		public function stopQueue():void{
			
			if(mIsLoading)mIsAbortLoading = true;
			
		}
		/**
		 * Starts the load queue
		 * 
		 */
		public function startQueue():void{
			if(!mIsLoading){
				mIsLoading = true;
				loadNext();
			}
		}
		
		/**
		 * flushes the load queue
		 * 
		 */
		public function flushQueue():void
		{	
			mLoadQueue = new Array();	
			resetVars();	
			
		}
		
		/**
		 * deconstructs and removes all IAssetInfo instances from the internal registery and flushes the load queue
		 * 
		 */
		public function flushAllAssets():void
		{			
			for each (var asset:IAssetInfo in mDataMap){
				asset.deconstruct();
			}
			mDataMap = new Dictionary();
			flushQueue();
			

		}
		
		/**
		 * deconstructs and removes all IAssetInfo instances from the internal registery, flushes the load queue
		 * and clears all registered clients
		 * 
		 */
		public function deconstruct():void
		{			
			
			mClientMap = new Object();
			flushAllAssets();
			

		}
		/**
		 * Registers a client with the Loader along with the id that maps to it.
		 * The clientType property of an IAssetInfo must be set to this before adding it to the queue
		 * @param id the id that maps to the client
		 * @param c the client as a class (must extend LoaderClientBase)
		 * @see org.puremvc.as3.multicore.utilities.unifiedloader.clients.LoaderClientBase
		 * 
		 */
		public function registerClient(id:String, c:Class):void
		{
			mClientMap[id] = c;
		}
		/**
		 * Removes the client mapped with the id 
		 * @param id the id that maps to the client
		 * 
		 */
		public function removeClient(id:String):void
		{
			if(mClientMap[id])
			{
				delete mClientMap[id];
			}
		}
		/**
		 * Gets the client mapped with the id 
		 * @param id the id that maps to the client
		 * @return the associated client as a Class
		 */
		public function getClient(id:String):Class{
			
			if (mClientMap[id] == null){

				throw new  RegistrationError (id);
			}
			
			return Class(mClientMap[id]);
		}
		
		private function init():void
		{
			mDataMap = new Dictionary();
			mClientMap = new Object();
			mLoadQueue = new Array();
			mIsAutoLoad = true;
		}
		
		
		
		private function loadNext():void
		{			
			var clazz:Class,
				client:LoaderClientBase,
				e:LoaderEvent,
				request:LoadRequest,
				url:String;
			
			if(mIsProrityChanged)sortQueue();
			
			url = LoadRequest(loadQueue.shift()).url;
			request = mDataMap[url];
			if(request == null){
				mNumInQueue--;
				mCount--;
				loadNext();
			}else{
				request.status = AssetInfoStatus.LOADING
				addListeners(request)
				
				clazz = getClient(request.clientType);
				client = new clazz(request);
				client.load();
				
				e = new LoaderEvent(	LoaderNotificationNames.ITEM_STARTED,
										mCurrentItemBytesLoaded,
										mCurrentItemBytesTotal,
										request.url,
										mNumInQueue,
										mNumInQueue - mCount,
										mQueueLoadProgress			);
				dispatchEvent(e)
			}
			
		}
		

		
		private function get loadQueue():Array{
			return mLoadQueue;
		}
		
		private function errorCheck(request:LoadRequest):void
		{
			switch (true)
			{
				case(request.status == AssetInfoStatus.LOAD_COMPLETE):
					throw new LoadQueueError (LoadQueueError.LOADED_ITEM);
					break;
					
				case(request.status == AssetInfoStatus.LOADING):
					throw new LoadQueueError (LoadQueueError.LOADING_ITEM);
					break;
					
				case(request.status == AssetInfoStatus.QUEUED):
					throw new LoadQueueError (LoadQueueError.QUEUED_ITEM);
					break;
					
				case(request.clientType == null):
					throw new LoadQueueError (LoadQueueError.NULL_CLIENT_TYPE)
					break
			}
				
		}
		
		private function sortQueue():void
		{
			if (mQueueSort != null)loadQueue.sort(mQueueSort);
			mIsProrityChanged = false;
		}
		
		
		private function storeRequestInMap(request:LoadRequest):void
		{
			if (!request.store)
			{
				delete mDataMap[request.url];
			}
			
		}
		
		private function endLoadCycleTest(request:LoadRequest):void
		{
			if(loadQueue.length > 0 && !mIsAbortLoading)
			{
				loadNext();	
				
			}else
			{	
				endLoadCycle(request);
				
			}
		}
		
		private function endLoadCycle(request:LoadRequest):void
		{
			var e:LoaderEvent;
			
			

			e = new LoaderEvent(	LoaderNotificationNames.QUEUE_COMPLETED,
									mCurrentItemBytesLoaded,
									mCurrentItemBytesTotal,
									request.url,
									mNumInQueue,
									mNumInQueue - mCount,
									mQueueLoadProgress			);
			resetVars();								
			dispatchEvent(e);
			
			
		}
		
		private function resetVars():void
		{
			if(mLoadQueue.length == 0){
				mCount = 0;
				mNumInQueue = 0;
				mQueueLoadProgress = 0;
			}
			
			mCurrentItemBytesLoaded = 0;
        	mCurrentItemBytesTotal = 0;

        	
        	mIsLoading = false;
			mIsAbortLoading = false;
		}

		private function addListeners(request:LoadRequest):void
		{
			
			request.loaderCallBack = clientCallBackHandler;
			
			request.addEventListener(ProgressEvent.PROGRESS, loadRequestProgressHandler);
			request.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler)
			request.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler)
			request.addEventListener(Event.CHANGE, priorityChangeHandler)
			
		}
		
		private function removeListeners(request:LoadRequest):void
		{
			request.loaderCallBack = null;
			
			request.removeEventListener(ProgressEvent.PROGRESS, loadRequestProgressHandler);
			request.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler)
			request.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler)
			request.removeEventListener(Event.CHANGE, priorityChangeHandler)
		}
		
		private function priorityChangeHandler(event:Event):void
		{
			mIsProrityChanged = true;
			
		}
		
		
		private function clientCallBackHandler(request:LoadRequest):void
		{
			
			loadRequestCompleteHandler(request);
				
		}
		
		private function loadRequestCompleteHandler(request:LoadRequest):void
		{
			var e:LoaderEvent;	
			
			mCount++;		
			
			removeListeners(request)
			storeRequestInMap(request);
			request.status = AssetInfoStatus.LOAD_COMPLETE;
			
			e = new LoaderEvent(	LoaderNotificationNames.ITEM_COMPLETED,
									mCurrentItemBytesLoaded,
									mCurrentItemBytesTotal,
									request.url,
									mNumInQueue,
									mNumInQueue - mCount,
									mQueueLoadProgress			);
										
			dispatchEvent(e);
			endLoadCycleTest(request);

		}
		
		private function securityErrorHandler(event:SecurityErrorEvent):void
		{
			var e:LoaderErrorEvent,
				request:LoadRequest;	
			
			request = LoadRequest(event.target);
			request.store = false;
			request.status = AssetInfoStatus.ERROR
			storeRequestInMap(request);
			removeListeners(request);
			mNumInQueue--;
			mCount--;
			
			e = new LoaderErrorEvent(	event.type, 
										event.text, 
										request.url	);
			dispatchEvent(e);
			endLoadCycleTest(request);  
			
		}
		
		private function ioErrorHandler(event:IOErrorEvent):void
		{
			var e:LoaderErrorEvent,
				request:LoadRequest;	
			
			request = LoadRequest(event.target);
			request.store = false;
			request.status = AssetInfoStatus.ERROR
			storeRequestInMap(request);
			removeListeners(request);
			mNumInQueue--;
			mCount--;
			
			e = new LoaderErrorEvent(	event.type, 
										event.text, 
										request.url	);
			dispatchEvent(e);
			endLoadCycleTest(request);  
			
		}
		
		private function loadRequestProgressHandler(event:ProgressEvent):void
		{
			var e:LoaderEvent,
				request:LoadRequest;
				
			request = LoadRequest(event.target);
			// this nice equation from QueueLoader @ hydrotik - thanks
			mQueueLoadProgress = (((mCount * (100 / (mNumInQueue))) + ((event.bytesLoaded / event.bytesTotal) * (100 / (mNumInQueue)))) * .01);
			
			
			mCurrentItemBytesLoaded = event.bytesLoaded;
			mCurrentItemBytesTotal = event.bytesTotal;
			
			
			e = new LoaderEvent(	LoaderNotificationNames.QUEUE_PROGRESS,
									mCurrentItemBytesLoaded,
									mCurrentItemBytesTotal,
									request.url,
									mNumInQueue,
									mNumInQueue - mCount,
									mQueueLoadProgress			);
											
			dispatchEvent(e)
			
		}

	}
	
}
